#include <array>
#include <sstream>

#if defined( WIN32 ) || defined( __MSC_VER )
#   include <windows.h>
#   include <dbghelp.h>
#else

#endif

#include "ary_str.hpp"
#include "mallocSharedPtr.hpp"
#include "misc.hpp"

#include "backtrace.hpp"

#if defined( __GNUC__ )
#include <cxxabi.h>
#endif
using namespace wheels;
using namespace dm;

IMP_SINGLETON( backtrace );

#if defined( _WIN32_WINNT ) || defined( __MSC_VER )
long __on_crash_handler( EXCEPTION_POINTERS * pException )
{
    std::string now_str = nowStr();
    std::wstring dump_file;
    std::wstringstream ss;
    ss << L"./Crash" << now_str.c_str() << L".dmp";
    dump_file = ss.str();

    HANDLE DumpHandle = CreateFile( dump_file.c_str() , GENERIC_WRITE , 0 , nullptr , CREATE_ALWAYS , FILE_ATTRIBUTE_NORMAL , nullptr );
    if( DumpHandle != INVALID_HANDLE_VALUE ){
        MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
        dumpInfo.ExceptionPointers = pException;
        dumpInfo.ThreadId = GetCurrentThreadId();
        dumpInfo.ClientPointers = TRUE;

        MiniDumpWriteDump( GetCurrentProcess() , GetCurrentProcessId() , DumpHandle , MiniDumpNormal , &dumpInfo , nullptr , nullptr );
        CloseHandle( DumpHandle );
    }
    return EXCEPTION_EXECUTE_HANDLER;
}
#else
#endif
backtrace :: backtrace( const std::string& file ):m_file__( file ),
    m_ofs__( file , std::ios_base::app )
{
#if defined( _WIN32_WINNT ) || defined( __MSC_VER )
    SetUnhandledExceptionFilter( (LPTOP_LEVEL_EXCEPTION_FILTER)__on_crash_handler );
#else

#endif
}

backtrace :: ~backtrace()
{

}
void backtrace :: dump()
{
    ArrayString data;

    read_info__( data );

    m_ofs__ << nowStr() << " --------------------------------------------\n";
    for( int i = data.size() - 1; i >= 0; --i ){
        m_ofs__ << data[ i ];
    }
}

#if defined( __GNUC__ ) && !defined( WIN32 )

void backtrace :: read_info__( ArrayString& data )
{
    const int maxStackDepth = 16;
    void* stackTrace[maxStackDepth];

    int numFrames = backtrace(stackTrace, maxStackDepth);
    char** symbols = backtrace_symbols(stackTrace, numFrames);

    if (numFrames > skipFrames && symbols != nullptr) {
        std::cout << "Call Stack Trace:\n";

        for (int i = skipFrames; i < numFrames; ++i) {
            size_t len;
            std::sstream ss;
            // Demangle the symbol name using abi::__cxa_demangle
            char* demangledSymbol = abi::__cxa_demangle(symbols[i], nullptr, &len, nullptr);

            if (demangledSymbol == nullptr || *demangledSymbol == '\0') {
                ss << "\t" << symbols[i] << "\n";
            } else {
                ss << "\t" << demangledSymbol << "\n";

                free(demangledSymbol);
            }

            data.push_back( ss.str() );
        }

        free(symbols);
    }
}

#elif defined( __GNUC__ ) || defined( __MSC_VER )
#include <windows.h>
#include <dbghelp.h>
void backtrace :: read_info__( wheels::ArrayString& data )
{
    HANDLE process = GetCurrentProcess();
    HANDLE thread = GetCurrentThread();

    CONTEXT context;
    memset(&context, 0, sizeof(context));
    context.ContextFlags = CONTEXT_FULL;

    RtlCaptureContext(&context);
    SymInitialize(process, NULL, true);

    DWORD image = IMAGE_FILE_MACHINE_AMD64;

    STACKFRAME64 stackframe;
    {
        ZeroMemory(&stackframe, sizeof(STACKFRAME64));

        // 请根据系统的arch自行填写
        stackframe.AddrPC.Offset    = context.Rip;
        stackframe.AddrPC.Mode      = AddrModeFlat;
        stackframe.AddrFrame.Offset = context.Rsp;
        stackframe.AddrFrame.Mode   = AddrModeFlat;
        stackframe.AddrStack.Offset = context.Rsp;
        stackframe.AddrStack.Mode   = AddrModeFlat;
    }

    mallocSharedPtr< char >   buff( 500 ) , buff1(1024);
    memset( buff1.get() , 0 , 1024 );
    for (size_t i = 0; i < 100; i++) {
        bool result = StackWalk( image, process, thread, &stackframe, &context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr );

        if (!result) { break; }

        std::array<char, sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)> buffer;
        PSYMBOL_INFO symbol = reinterpret_cast<PSYMBOL_INFO>(buffer.data());
        symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
        symbol->MaxNameLen = MAX_SYM_NAME;

        DWORD64 displacement = 0;

        if (SymFromAddr(process, stackframe.AddrPC.Offset, &displacement, symbol)) {
            sprintf(buff.get() , "[%llu] 0X%llX %s\n", i, symbol->Address, symbol->Name );
        } else {
            sprintf(buff.get() , "[%llu] 0X%llX ???\n", i, symbol->Address );
        }

        data.push_back( std::string( buff.get() ) );
    }

    SymCleanup(process);
}

#endif
